Categoría: PHP

  • Capas invisibles de seguridad en PHP que bloquean ataques

    Capas invisibles de seguridad en PHP que bloquean ataques

    Resumen: las “capas invisibles” son defensas integradas en el runtime o en la capa infraestrutural que bloquean clases completas de ataques antes de que lleguen al negocio. Este artículo describe seis capas prácticas y muestra ejemplos de implementación en PHP.

    Introducción

    Los atacantes buscan huecos repetibles y fáciles: salidas sin escapar, sesiones que no cambian, consultas sin parámetros. Las capas invisibles obligan al runtime o a la infraestructura a aplicar defensas por defecto, reduciendo la carga mental del desarrollador y cerrando vectores comunes.

    Prerrequisitos

    Antes de integrar estas capas conviene contar con un entorno que permita soluciones como hashing moderno, plantillas con auto-escaping y mecanismos de almacenamiento para límites por IP.

    • PHP con soporte para Argon2id (mencionado en el texto base).
    • Motor de plantillas con auto-escaping (por ejemplo, Twig o Blade, o equivalente).
    • Acceso a la base de datos vía ORM o biblioteca que permita prepared statements.
    • Sistema de caché o store (Redis, memcached, etc.) para rate-limiting y rotación de tokens.

    Desarrollo

    Procedimiento

    Capa 1 — Auto-escaping a nivel de motor de plantillas: configure el motor para escapar por defecto las salidas. Así, incluso si un desarrollador olvida sanitizar una variable, el motor evita XSS a la salida.

    Capa 2 — Hashing memory-hard para credenciales: use algoritmos que consuman memoria (Argon2id o scrypt). Según el texto base, Argon2id es la opción recomendada para evitar que dumps de contraseñas sean fáciles de crackear.

    Capa 3 — Rotación automática de tokens: regenere identificadores de sesión en eventos críticos (login, elevación de privilegios) y mantenga un tiempo de validez corto para tokens expuestos.

    Capa 4 — Firewall de consultas dentro del ORM: haga que el acceso a la BD use solo consultas parametrizadas. Si el ORM obliga a parámetros, las inyecciones se eliminan en la práctica.

    Capa 5 — Middleware adaptativo a la tasa (rate-adaptive): aumente la latencia o limite intentos según patrones sospechosos. El objetivo es hacer el brute force económicamente inviable sin afectar a usuarios legítimos.

    Capa 6 — Content Security Policy (CSP): entregue cabeceras CSP estrictas para limitar fuentes de scripts, iframes y orígenes. Esto reduce el impacto de XSS y evita exfiltración desde el navegador.

    Ejemplos

    A continuación hay ejemplos concisos para poner en práctica las capas descritas.

    <?php
    // Hashing con Argon2id
    $password = 'usuario-password';
    $options = ['memory_cost' => 1<<17, 'time_cost' => 4, 'threads' => 2];
    $hash = password_hash($password, PASSWORD_ARGON2ID, $options);
    Lenguaje del código: PHP (php)

    Ejemplo de consultas parametrizadas con PDO (ORMs modernos aplican esto por defecto).

    <?php
    $stmt = $pdo->prepare('SELECT id, email FROM users WHERE email = :email');
    $stmt->execute([':email' => $email]);
    $user = $stmt->fetch();
    Lenguaje del código: PHP (php)

    Rotación de token/ID de sesión en eventos clave.

    <?php
    session_start();
    // Tras login o cambio de privilegios
    session_regenerate_id(true);
    $_SESSION['rotated_at'] = time();
    // Verificar caducidad en cada petición
    if (isset($_SESSION['rotated_at']) && time() - $_SESSION['rotated_at'] > 3600) {
        // forzar logout o revalidación
    }
    Lenguaje del código: PHP (php)

    Cabecera CSP mínima para reducir ejecución de scripts externos.

    <?php
    header("Content-Security-Policy: default-src 'self'; script-src 'self'");
    Lenguaje del código: PHP (php)

    Middleware simple para introducir fricción adaptativa en intentos de login.

    <?php
    $ip = $_SERVER['REMOTE_ADDR'];
    $attempts = $cache->get("login:{$ip}") ?? 0;
    if ($attempts > 5) {
        // añadir demora creciente (ej. usleep) para frustrar ataques automatizados
        usleep(min(500000 * ($attempts - 5), 2000000));
    }
    $cache->set("login:{$ip}", $attempts + 1, 300);
    Lenguaje del código: PHP (php)

    Checklist

    1. Habilitar auto-escaping en el motor de plantillas.
    2. Usar hashing memory-hard (Argon2id) para contraseñas.
    3. Forzar consultas parametrizadas desde el ORM o capa de datos.
    4. Implementar rotación de tokens y control de caducidad.
    5. Agregar middleware rate-adaptive para endpoints sensibles.
    6. Entregar cabeceras CSP estrictas y revisar políticas periódicamente.

    Conclusión

    Las capas invisibles eliminan vectores enteros de ataque al aplicar buenas prácticas desde el runtime y la infraestructura. Implementarlas reduce la superficie de ataque y permite a los equipos centrarse en la lógica de negocio con mayor confianza.

  • Autoload adaptativo en PHP: reducir tiempo de descubrimiento de archivos

    Autoload adaptativo en PHP: reducir tiempo de descubrimiento de archivos

    Este artículo explica cómo reducir el tiempo de descubrimiento de archivos en aplicaciones PHP mediante un autoload adaptativo que convierte mapas estáticos en una caché predictiva.

    Incluye patrón mínimo, integración con Composer y ejemplos prácticos para precargar y persistir la caché.

    Introducción

    Un autoloader estándar (por ejemplo PSR-4 con Composer) mapea namespaces a directorios y busca secuencialmente hasta encontrar un archivo. En proyectos grandes esa búsqueda repetida agrega latencia acumulada.

    El autoload adaptativo crea y mantiene una caché de rutas resueltas (en memoria o en disco) que se actualiza cuando cambia el árbol de clases, transformando búsquedas costosas en accesos determinísticos y rápidos.

    Prerrequisitos

    • Proyecto PHP que use autoload (Composer o autoloader propio).
    • Permiso para escribir un archivo de caché en disco (si se persiste entre peticiones).
    • Posibilidad de ejecutar scripts de Composer (si se integra con ganchos post-autoload-dump).
    • Opcional: soporte para opcache.preload para precargar rutas en entornos que lo permiten.

    Desarrollo

    Procedimiento

    La idea central es mantener un mapa (hash) de clase => ruta. En runtime se consulta primero esa caché; si la entrada existe se incluye la ruta inmediatamente. Si no, el autoloader realiza la búsqueda clásica, almacena el resultado en la caché y continúa.

    <?php
    spl_autoload_register(function ($class) {
        static $cache = [];
    
        if (isset($cache[$class])) {
            require $cache[$class];
            return;
        }
    
        $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
        if (file_exists($file)) {
            $cache[$class] = $file;
            require $file;
        }
    });
    Lenguaje del código: PHP (php)

    Ese ejemplo mantiene la caché en memoria. Para persistir entre procesos, vuelca el array a JSON en disco después de cambios relevantes y vuelves a cargarlo al arrancar.

    Usar opcache.preload junto con una caché prellenada reduce aún más I/O en solicitudes recurrentes: el mapa pre-cargado evita lecturas repetidas desde disco.

    Integración con Composer

    Puedes enganchar un script a post-autoload-dump para reconstruir la caché adaptativa cuando Composer actualice el autoload. Ejemplo de snippet en composer.json:

    {
      "scripts": {
        "post-autoload-dump": [
          "php scripts/build-classmap-cache.php"
        ]
      }
    }
    Lenguaje del código: JSON / JSON con comentarios (json)

    En el script de construcción puedes combinar composer/autoload_classmap.php con tu caché adaptativa para obtener una base precisa y añadir hits observados en producción.

    <?php
    // scripts/build-classmap-cache.php
    $classmap = [];
    $composerMap = __DIR__ . '/../vendor/composer/autoload_classmap.php';
    if (file_exists($composerMap)) {
        $classmap = include $composerMap; // devuelve array clase => ruta
    }
    // Aquí podrías mergear con una caché existente o con datos de telemetría
    file_put_contents(__DIR__ . '/../storage/classmap-cache.json', json_encode($classmap, JSON_PRETTY_PRINT));
    Lenguaje del código: PHP (php)

    El ejemplo escribe un archivo JSON que el autoloader puede cargar al inicio para evitar búsquedas en disco en la primera petición.

    Ejemplos

    Lectura de la caché pre-generada en el arranque de la aplicación antes de registrar el autoloader dinámico:

    <?php
    $cacheFile = __DIR__ . '/storage/classmap-cache.json';
    $cache = [];
    if (file_exists($cacheFile)) {
        $cache = json_decode(file_get_contents($cacheFile), true);
    }
    
    spl_autoload_register(function ($class) use (&$cache) {
        if (isset($cache[$class])) {
            require $cache[$class];
            return;
        }
    
        // Fallback: búsqueda clásica y almacenamiento en caché
        $file = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
        if (file_exists($file)) {
            $cache[$class] = $file;
            require $file;
            // Opcional: persistir incrementalmente o marcar para persistencia en shutdown
        }
    });
    Lenguaje del código: PHP (php)

    Este flujo permite que la primera petición use la caché en disco y las siguientes usen la versión en memoria, con posibilidad de actualizar el archivo cuando cambien las rutas.

    Checklist

    1. Generar una base de classmap (composer/autoload_classmap.php) o similar.
    2. Implementar caché en memoria y opcional persistencia en disco (JSON).
    3. Registrar autoloader que consulte la caché antes de buscar en disco.
    4. Integrar con Composer: script post-autoload-dump para reconstruir la caché cuando cambien dependencias.
    5. Considerar opcache.preload y precargar la caché en entornos que lo permitan.
    6. Medir y validar: comparar tiempo de arranque y perfil de I/O antes y después.

    Conclusión

    Un autoload adaptativo reduce búsquedas innecesarias convirtiendo mapas estáticos en una caché viva que aprende patrones de carga. El resultado: menos I/O, resolución de clases casi instantánea y mayor predictibilidad en perfiles de rendimiento.

    Implementa la caché en memoria con persistencia opcional, engancha la reconstrucción a Composer y considera opcache.preload para optimizar el comportamiento en producción.

  • Guía rápida de attributes en PHP 8+: uso y ejemplos

    Guía rápida de attributes en PHP 8+: uso y ejemplos

    Los attributes en PHP 8+ permiten adjuntar metadatos nativos a clases, métodos, propiedades y parámetros. Esta guía muestra cómo definirlos, aplicarlos y leerlos con Reflection, incluyendo ejemplos prácticos.

    Introducción

    Antes de PHP 8 se usaban docblocks y parsing de comentarios para añadir metadatos (por ejemplo, rutas). Los attributes son nativos: son clases usadas como metadatos mediante sintaxis literal, p. ej. #[Route('/users')].

    Prerrequisitos

    Necesitas PHP 8.0+ (los attributes son una característica introducida con PHP 8). También conviene familiaridad básica con clases y la API de Reflection para leer atributos en tiempo de ejecución.

    Desarrollo

    Procedimiento

    1. Crear la clase que representará el attribute y marcarla con #[Attribute].
    2. Aplicar el attribute sobre clases, métodos, propiedades o parámetros según correspondan.
    3. Leer los atributos en tiempo de ejecución usando Reflection y, si hace falta, instanciarlos.

    A continuación hay snippets que ilustran cada paso: definición, uso y lectura por Reflection.

    <?php
    #[Attribute(Attribute::TARGET_METHOD)]
    class Route
    {
        public function __construct(
            public string $path,
            public array $methods = ['GET']
        ) {}
    }
    Lenguaje del código: PHP (php)
    <?php
    class UserController
    {
        #[Route('/users', methods: ['GET'])]
        public function index() {}
    }
    Lenguaje del código: PHP (php)
    <?php
    $method = new ReflectionMethod(UserController::class, 'index');
    $attributes = $method->getAttributes();
    
    foreach ($attributes as $attribute) {
        $instance = $attribute->newInstance();
        var_dump($instance->path); // '/users'
    }
    Lenguaje del código: PHP (php)

    Atributos nativos

    PHP incluye varios attributes útiles. Aquí hay ejemplos prácticos tomados de la sintaxis nativa que aparecen en el lenguaje.

    Ejemplo: #[ReturnTypeWillChange] — se usa para compatibilidad de firmas en bibliotecas.

    <?php
    class MyIterator implements Iterator
    {
        #[ReturnTypeWillChange]
        public function current() {
            return 'foo';
        }
    }
    Lenguaje del código: PHP (php)

    Ejemplo: #[Deprecated] — marca elementos en desuso y puede incluir un motivo.

    <?php
    #[Deprecated(reason: 'Use newFunction() instead.')]
    function oldFunction() {
        return 'legacy';
    }
    Lenguaje del código: PHP (php)

    Ejemplo: #[AllowDynamicProperties] — re-habilita propiedades dinámicas en clases legadas.

    <?php
    #[AllowDynamicProperties]
    class User {}
    
    $user = new User();
    $user->name = 'Lukasz'; // funciona
    Lenguaje del código: PHP (php)

    Ejemplo: #[SensitiveParameter] — oculta valores en trazas y dumps para parámetros sensibles.

    <?php
    function login(#[SensitiveParameter] string $password) {
        throw new Exception('Oops!');
    }
    
    login('super-secret-password');
    Lenguaje del código: PHP (php)

    Ejemplo: #[Override] — asegura que un método realmente sobrescribe uno del padre.

    <?php
    class Base {
        public function greet() {}
    }
    
    class Child extends Base {
        #[Override]
        public function greet() {
            echo "Hello!";
        }
    }
    Lenguaje del código: PHP (php)

    Ejemplos

    Combinar atributos con frameworks o bibliotecas propias suele reducir la necesidad de parsear docblocks y simplifica extractores de metadatos. Un patrón común: definir attributes para rutas, validar parámetros sensibles y marcar API obsoletas.

    Ejemplo práctico rápido: definir una ruta y extraerla desde un bootstrap de aplicación para registrar endpoints.

    <?php
    // Definición ya mostrada arriba: Route
    // En bootstrap de la app:
    $rc = new ReflectionClass(UserController::class);
    foreach ($rc->getMethods() as $method) {
        foreach ($method->getAttributes() as $attr) {
            $route = $attr->newInstance();
            // registrar $route->path y $route->methods en el router
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Reemplazar parsing de docblocks por classes-attribute cuando sea posible.
    2. Marcar las classes de attribute con #[Attribute] y ajustar TARGET_* según uso.
    3. Actualizar extractores para usar Reflection::getAttributes().
    4. Probar casos de compatibilidad: #[ReturnTypeWillChange] y #[AllowDynamicProperties] si persisten dependencias legadas.
    5. Marcar parámetros sensibles con #[SensitiveParameter] para proteger trazas.

    Conclusión

    Los attributes son una mejora clara frente a los docblocks: son nativos, más seguros y fáciles de consumir por código. Migrar a attributes reduce parsing ad hoc y coloca la metadata junto al código que la usa.

    Si estás manteniendo una librería o framework en PHP, prueba a implementar un pequeño atributo y su extractor: la reducción de complejidad suele notarse rápido.

  • Reciclaje de memoria en workers PHP de larga ejecución

    Reciclaje de memoria en workers PHP de larga ejecución

    Resumen: técnicas prácticas para controlar y reciclar memoria en procesos PHP de larga ejecución (queues, daemons, RoadRunner/Swoole) y mantener la huella de memoria estable.

    Introducción

    Los workers PHP de larga ejecución (colas, daemons, RoadRunner, Swoole) tienden a crecer en memoria con el tiempo: referencias no liberadas, objetos grandes retenidos y fragmentación del gestor de memoria provocan aumento del RSS y degradación del rendimiento.

    Este artículo condensa estrategias prácticas para reciclar memoria sin recurrir a reinicios agresivos por defecto: límites controlados, limpieza explícita y patrones de procesamiento que reducen la presión de memoria.

    • Referencias retenidas en closures y objetos con referencias circulares.
    • Variables estáticas que acumulan cachés ilimitados.
    • Procesamiento en lotes demasiado grandes (arrays/objetos en memoria).
    • Fragmentación interna del gestor de memoria de Zend o fugas en extensiones (ej.: pdo_mysql).

    Prerrequisitos

    Antes de aplicar estas técnicas asegúrate de tener control sobre la ejecución del worker y acceso a las extensiones/funciones necesarias.

    • PHP CLI con soporte para pcntl si vas a implementar reinicios controlados.
    • Capacidad de supervisión/respawn (supervisor, systemd, o el propio orquestador de RoadRunner/Swoole).
    • Acceso a herramientas de monitorización (ps, pmap, logs de memoria).

    Desarrollo

    Procedimiento

    1. Establecer límites y reinicios controlados.
    2. Eliminar referencias y forzar recolección de ciclos.
    3. Procesar en chunks para evitar picos de memoria.
    4. Reutilizar objetos pesados (pooling) en vez de recrearlos constantemente.
    5. Monitorizar tendencias y métricas de uso de memoria.

    A continuación ejemplos de implementación de cada punto crítico.

    1) Reinicio controlado: comprobar uso de memoria y salir para que el supervisor recree el proceso.

    <?php
    // dentro del loop principal del worker
    if (memory_get_usage(true) > 256 * 1024 * 1024) {
        // salir para que el supervisor o manager respawnee un proceso limpio
        exit(1);
    }
    Lenguaje del código: PHP (php)

    2) Limpieza explícita de referencias y recolección de ciclos: unset no siempre basta si existen referencias circulares.

    <?php
    // después de procesar una tarea pesada
    unset($largeStructure);
    // forzar recolección de ciclos si hay objetos con referencias circulares
    gc_collect_cycles();
    Lenguaje del código: PHP (php)

    3) Chunking: evitar cargar datasets completos en memoria. Procesa por lotes y libera cada lote.

    <?php
    foreach (array_chunk($rows, 5000) as $chunk) {
        process($chunk);
        unset($chunk);
        gc_collect_cycles();
    }
    Lenguaje del código: PHP (php)

    4) Reutilización (pooling): evita crear y destruir objetos pesados frecuentemente. Mantén conexiones o parsers en pool cuando sea seguro.

    5) Monitorización: usa herramientas del sistema y funciones internas para trazar tendencias de memoria y detectar fugas tempranas.

    ps -o pid,rss,cmd -C php
    # o para inspección puntual de PID
    pmap <PID> | tail -n 1
    Lenguaje del código: Bash (bash)

    Ejemplos

    Ejemplo de loop de worker que combina chequeo de memoria, chunking y recolector de ciclos.

    <?php
    // worker.php - bucle simplificado
    while (true) {
        $rows = fetchPendingRows(50000); // pseudocódigo
    
        foreach (array_chunk($rows, 5000) as $chunk) {
            process($chunk);
            unset($chunk);
            gc_collect_cycles();
        }
    
        // chequeo de memoria: salir si supera 256MB para que el supervisor reinicie
        if (memory_get_usage(true) > 256 * 1024 * 1024) {
            exit(1);
        }
    
        // dormir o esperar por nueva tarea
        usleep(100000);
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Habilitar y probar reinicios controlados (pcntl o supervisión externa).
    2. Identificar y eliminar referencias circulares; usar gc_collect_cycles tras tareas pesadas.
    3. Procesar en chunks y liberar cada lote explícitamente.
    4. Reutilizar objetos pesados mediante pooling cuando sea seguro.
    5. Registrar y monitorizar memoria (memory_get_peak_usage, ps, pmap) para detectar tendencias.

    Trata la memoria como una moneda: mézclala, recíclala y no la derroches en procesos de larga vida.

    Conclusión

    Workers PHP de larga ejecución son útiles, pero requieren disciplina: límites claros, limpieza explícita y monitorización. Implementa reinicios controlados, limpia referencias, procesa por chunks y registra tendencias para mantener la huella de memoria saneada.

    Si no tienes información para afirmar compatibilidades o comportamientos específicos de versiones en tu entorno, prueba primero en un entorno de staging y mide antes de aplicar en producción.

  • Dejar que PHP narre su runtime: guía práctica

    Dejar que PHP narre su runtime: guía práctica

    \n

    Resumen: este artículo muestra cómo aplicar un patrón de \”Narrator\” en PHP para que la propia aplicación narre su flujo de ejecución. Incluye un diseño básico, integración y ejemplos de código listos para adaptar.

    \n\n\n\n\n\n\n\n

    Introducción

    \n\n\n\n

    Las soluciones tradicionales de observabilidad entregan señales reactivas: métricas, trazas y logs que requieren correlación externa. En lugar de depender exclusivamente de herramientas externas, podemos hacer que la aplicación misma produzca una narración del runtime: eventos con contexto e intención unidos en hilos causales.

    \n\n\n\n

    Este enfoque complementa (no necesariamente reemplaza) la observabilidad convencional: su objetivo es mejorar la comprensibilidad interna —por ejemplo, por qué se tomó una decisión— y facilitar la depuración rápida sin saltar entre múltiples herramientas.

    \n\n\n\n

    Prerrequisitos

    \n\n\n\n
    • Entorno PHP donde puedas anexar objetos al ciclo de vida de la petición (middleware o front controller).
    • Un logger compatible PSR-3 (por ejemplo Monolog) o un adaptador propio para exportar hilos de eventos cuando sea necesario.
    • Mínima estructura para identificar contexto: request id, session id o user id para correlación.
    \n\n\n\n

    Desarrollo

    \n\n\n\n

    Procedimiento

    \n\n\n\n
    1. Diseñar una clase Narrator que viaje con la petición y acumule eventos con metadatos (tipo, intención, contexto, timestamp).
    2. Instrumentar puntos clave: routing, validaciones, intentos de reintento, decisiones que afecten el flujo.
    3. Decidir retención: caducar segmentos de memoria cuando ya no aporten valor, o agrupar eventos en hilos causales.
    4. Exportar o exponer los hilos en un formato que un visor timeline pueda consumir (JSON, evento estructurado a logger, API interna).
    \n\n\n\n

    A continuación se muestra una implementación mínima de una clase Narrator que acumula eventos y permite serializarlos. Es un punto de partida para adaptar a frameworks o middlewares.

    \n\n\n\n
    <?php\nnamespace App\Observability;\n\nclass Narrator\n{\n    private array $threads = [];\n    private array $currentContext = [];\n\n    public function startThread(string $id, array $meta = []): void\n    {\n        $this->threads[$id] = [\n            'id' => $id,\n            'meta' => $meta,\n            'events' => [],\n        ];\n    }\n\n    public function annotate(string $threadId, string $type, string $message, array $context = []): void\n    {\n        $this->threads[$threadId]['events'][] = [\n            'ts' => microtime(true),\n            'type' => $type,\n            'message' => $message,\n            'context' => $context,\n        ];\n    }\n\n    public function setContext(array $context): void\n    {\n        $this->currentContext = $context;\n    }\n\n    public function expireThread(string $threadId): void\n    {\n        unset($this->threads[$threadId]);\n    }\n\n    public function export(): array\n    {\n        return $this->threads;\n    }\n}\n
    \n\n\n\n

    Integrar el Narrator con el flujo de la petición (por ejemplo como middleware) permite que cada controlador o servicio registre decisiones y motivos, y al final de la petición exporte el hilo a un logger o lo envíe a un endpoint interno.

    \n\n\n\n
    <?php\n// Ejemplo simplificado de middleware (framework-agnóstico)\nuse App\\Observability\\Narrator;\nuse Psr\\Log\\LoggerInterface;\n\nclass NarratorMiddleware\n{\n    private Narrator $narrator;\n    private LoggerInterface $logger;\n\n    public function __construct(Narrator $narrator, LoggerInterface $logger)\n    {\n        $this->narrator = $narrator;\n        $this->logger = $logger;\n    }\n\n    public function handle($request, $next)\n    {\n        $requestId = $request->getAttribute('request_id') ?? uniqid('req_', true);\n        $this->narrator->startThread($requestId, ['path' => $request->getUri()]);\n        $this->narrator->setContext(['request_id' => $requestId]);\n\n        $response = $next($request);\n\n        // Al final de la petición exportamos el hilo\n        $threads = $this->narrator->export();\n        $this->logger->info('narrator.threads', ['threads' => $threads]);\n\n        return $response;\n    }\n}\n
    \n\n\n\n

    En el ejemplo anterior, el logger recibe la estructura completa; un visor de timeline o un consumidor puede mostrar los eventos como una historia por petición. También puede enviarse a un índice o guardarse en almacenamiento temporal.

    \n\n\n\n
    {\n  \"req_606e2a\": {\n    \"id\": \"req_606e2a\",\n    \"meta\": {\"path\": \"/checkout\"},\n    \"events\": [\n      {\"ts\": 1690000000.123, \"type\": \"decision\", \"message\": \"cookie override - route B\", \"context\": {\"cookie\": \"promo\"}},\n      {\"ts\": 1690000000.456, \"type\": \"intent\", \"message\": \"calculate delivery estimate\", \"context\": {\"address\": \"...\"}},\n      {\"ts\": 1690000000.789, \"type\": \"notice\", \"message\": \"missing delivery estimate - user exited\", \"context\": {}}\n    ]\n  }\n}\n
    \n\n\n\n

    Ejemplos

    \n\n\n\n

    Ejemplo de uso dentro de una función de negocio: anotar la intención antes de ejecutar y el resultado después. Así se preserva la causalidad entre intención y efecto.

    \n\n\n\n
    <?php\n// Dentro de un servicio\nfunction applyCoupon(Narrator $narrator, string $threadId, array $couponData)\n{\n    $narrator->annotate($threadId, 'intent', 'apply coupon', ['coupon' => $couponData['code']]);\n\n    // Lógica real\n    $applied = false;\n    // ... comprobar validaciones, límites, fecha ...\n\n    if ($applied) {\n        $narrator->annotate($threadId, 'result', 'coupon applied', ['discount' => 10]);\n    } else {\n        $narrator->annotate($threadId, 'result', 'coupon rejected', ['reason' => 'expired']);\n    }\n\n    return $applied;\n}\n
    \n\n\n\n

    Con esta información, los equipos de producto y soporte pueden leer la cadena de eventos y comprender qué decisión tomó la aplicación y por qué, sin reconstruir el estado a partir de múltiples fuentes.

    \n\n\n\n

    Checklist

    \n\n\n\n
    1. Decidir el alcance de narración: qué tipos de eventos y qué contexto incluir.
    2. Agregar un objeto Narrator accesible en el ciclo de vida de la petición.
    3. Instrumentar puntos críticos: routing, validaciones, retries, fallos y decisiones de negocio.
    4. Definir política de expiración o agregación para evitar ruido innecesario.
    5. Elegir destino de exportación: logger estructurado, índice temporal o UI de timeline.
    6. Validar que los eventos incluyan identificadores para correlación (request id, session id).
    \n\n\n\n

    Conclusión

    \n\n\n\n

    Hacer que PHP narre su propio runtime reduce la necesidad de interpretar trazas desconectadas y acelera la resolución de problemas. Empieza por una implementación pequeña: una clase Narrator, puntos de instrumentación selectos y un canal para exportar hilos.

    \n\n\n\n

    Este patrón no elimina herramientas de observabilidad, pero aporta una capa de contexto y causalidad que hace a las aplicaciones más autoexplicativas y a los equipos menos dependientes de correlaciones externas.

    \n\n
  • Errores comunes de DDD en PHP y cómo evitarlos

    Resumen: este artículo recopila los errores más comunes al aplicar Domain-Driven Design en PHP y propone alternativas prácticas para mejorar nombres, estructura de código, modelos y límites entre capas.

    Introducción

    DDD no es una receta fija: son decisiones de modelado. Este artículo identifica malas prácticas frecuentes en proyectos PHP y muestra alternativas que ayudan a mantener el dominio expresivo, testable y consistente.

    Prerrequisitos

    Se asume familiaridad básica con PHP moderno, PSR, namespaces y conceptos OOP (clases, interfaces, visibilidad). También conviene conocer el vocabulario básico de DDD (aggregate, repository, value object).

    Desarrollo

    A continuación se resumen problemas comunes y recomendaciones concretas extraídas del análisis de proyectos reales.

    Procedimiento

    1. Forzar y usar un lenguaje ubicuo (ubiquitous language) consistente en nombres, namespaces y propiedades.
    2. Preferir una organización de dominio orientada al modelo (Model) en lugar de carpetas técnicas por patrón.
    3. Usar repositorios que trabajen con agregados; evitar DAOs que exponen CRUD arbitrario.
    4. Generar identidad en el dominio (UUID/ULID o una estrategia de nextIdentity en el repositorio).
    5. Diferenciar claramente PO (Parameter Object), VO (Value Object) y DTO y aplicar cada uno en su lugar.

    Cada punto se ilustra con ejemplos y patrones a continuación.

    Ejemplos

    Namespaces y raíz significativa — evite App si puede usar un namespace alineado al dominio:

    <?php
    namespace App\IAM\Domain\User;
    
    // Mejor: nombre alineado al contexto de negocio
    
    namespace ECommerce\IAM\Domain\User;
    Lenguaje del código: PHP (php)

    Organización de carpetas: agrupar por Model en lugar de fragmentar por patrones tácticos mejora la navegación y la expresividad.

    <?php
    namespace ECommerce\IAM\Domain\Model\User;
    namespace ECommerce\IAM\Domain\Model\Credentials;
    Lenguaje del código: PHP (php)

    Nombre de repositorio y propiedad inyectada: prefiera nombres del dominio en la propiedad para leer mejor el código.

    <?php
    namespace ECommerce\IAM\Application\CreateUser;
    
    use ECommerce\IAM\Domain\Model\UserRepository;
    
    class CreateUserHandler
    {
        public function __construct(private readonly UserRepository $users) {}
    
        public function __invoke(CreateUserCommand $command)
        {
            $user = User::create($command->email, $command->password);
    
            $this->users->add($user);
        }
    }
    Lenguaje del código: PHP (php)

    Getters: en DDD es preferible usar nombres que reflejen el dominio (username() en vez de getUsername()).

    <?php
    final class User
    {
        private string $username;
    
        public function username(): string
        {
            return $this->username;
        }
    }
    Lenguaje del código: PHP (php)

    Anémico vs rico: coloque comportamiento y reglas en la entidad para proteger invariantes.

    <?php
    // Anémico (anti-pattern)
    final class User
    {
        public function __construct(
            private Uuid $id,
            private string $name,
            private string $email,
        ) {}
    
        public function getName(): string { return $this->name; }
        public function setName(string $name): void { $this->name = $name; }
    }
    
    // Rico (recomendado)
    final class User
    {
        public function __construct(
            private Uuid $id,
            private string $name,
            private string $email,
        ) {}
    
        public function changeName(string $newName): void
        {
            // validar reglas de negocio
            $this->name = $newName;
        }
    }
    Lenguaje del código: PHP (php)

    DAO vs Repository: un DAO expone CRUD de bajo nivel; un Repository trabaja con agregados y oculta la persistencia.

    <?php
    // DAO (operaciones SQL, arreglos)
    final class UserDAO
    {
        public function find(int $id): array { /* ... */ }
        public function insert(array $data): void { /* ... */ }
        public function update(int $id, array $data): void { /* ... */ }
    }
    
    // Repository (trabaja con agregados)
    final class MySQLUserRepository implements UserRepository
    {
        public function byId(UserId $id): ?User { /* ... */ }
        public function add(User $user): void { /* ... */ }
    }
    Lenguaje del código: PHP (php)

    Identidad: evite depender de auto-increment del motor. Genere identidad en el dominio (UUID/ULID) o exponga nextIdentity() desde el repositorio.

    <?php
    use Ramsey\Uuid\Uuid;
    
    final class UserId
    {
        private function __construct(private string $value) {}
    
        public static function generate(): self
        {
            return new self(Uuid::uuid4()->toString());
        }
    
        public function asString(): string
        {
            return $this->value;
        }
    }
    Lenguaje del código: PHP (php)

    DTO, Parameter Object y Value Object: defínalos por su propósito y no los confundas.

    <?php
    // Parameter Object: agrupa parámetros para llamadas
    deconstructing
    readonly class Credentials
    {
        public function __construct(public string $email, public string $password) {}
    }
    
    // DTO: usado para exponer datos fuera del dominio
    final readonly class UserShowDTO
    {
        public function __construct(public string $email, public string $name) {}
    }
    
    // Value Object: inmutable y con invariantes
    final readonly class FullName
    {
        private function __construct(private string $firstName, private string $lastName) {}
    
        public static function create(string $firstName, string $lastName): self
        {
            // validar invariantes aquí
            return new self($firstName, $lastName);
        }
    
        public function asString(): string
        {
            return $this->firstName . ' ' . $this->lastName;
        }
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Revisar nombres y namespaces: ¿hablan el lenguaje del dominio?
    2. ¿La estructura de carpetas refleja conceptos del negocio (Model) en vez de patrones técnicos?
    3. ¿Las entidades encapsulan comportamiento o son solo DTOs con setters?
    4. ¿Los repositorios trabajan con agregados y no exponen update() genérico?
    5. ¿La identidad se genera en el dominio (UUID/nextIdentity) en lugar de setId() posterior a la persistencia?
    6. ¿Se diferencian claramente PO, VO y DTO en los límites del sistema?

    Conclusión

    DDD efectivo en PHP depende de decisiones de modelado: imponer lenguaje ubicuo, poner comportamiento en las entidades, usar repositorios centrados en agregados y mantener fronteras claras entre PO/VO/DTO. Aplicar estas prácticas mejora claridad, seguridad y testabilidad.

    Si quieres, puedo preparar una segunda parte con más ejemplos prácticos o refactorizaciones paso a paso.

  • Trucos PHP para evitar picos de CPU en producción

    Trucos PHP para evitar picos de CPU en producción

    Este artículo resume tácticas prácticas y verificables para reducir picos de CPU en aplicaciones PHP en producción. Se centra en cambios puntuales: bucles, caché, extensiones y preload.

    Introducción

    Los picos de CPU suelen ser consecuencia de patrones repetitivos: bucles descontrolados, reconstrucciones de caché simultáneas, operaciones de cálculo en PHP y bloqueos que consumen ciclos. Aquí verás acciones concretas para reducir su impacto sin reescribir todo el stack.

    Prerrequisitos

    Antes de aplicar las tácticas: asegúrate de tener monitoreo de CPU y trazas, un sistema de caché (p. ej. Redis) y OPcache habilitado. Algunas recomendaciones asumen disponibilidad de extensiones (GMP/BCMath) o mecanismos de event loop si se migran procesos largos.

    opcache.enable=1
    opcache.preload=/var/www/preload.php
    Lenguaje del código: TOML, también INI (ini)

    Desarrollo

    Procedimiento

    A continuación se describen las tácticas principales, con ejemplos y recomendaciones de implementación mínima para reducir picos de CPU.

    1) Controlar bucles y procesamiento por lotes: no iteres colecciones potencialmente enormes en una sola pasada; procesa en chunks y paginación para limitar uso de CPU por iteración.

    <?php
    foreach ($users as $user) {
        process($user);
    }
    Lenguaje del código: PHP (php)
    <?php
    foreach (array_chunk($users, 500) as $batch) {
        foreach ($batch as $user) {
            process($user);
        }
    }
    Lenguaje del código: PHP (php)

    2) Caché: evita el “dogpile” (cache stampede) usando coalescencia de peticiones; solo un worker debe reconstruir el caché mientras los demás leen la versión existente.

    <?php
    if ($lock = $redis->setnx("lock:feed", 1)) {
        $redis->expire("lock:feed", 30);
        $data = buildFeed();
        $redis->set("cache:feed", $data, 300);
    } else {
        $data = $redis->get("cache:feed");
    }
    Lenguaje del código: PHP (php)

    3) Delegar cálculos pesados a extensiones o bibliotecas nativas. Para parsing/validación de JSON o aritmética de gran precisión, usar extensiones C (GMP, BCMath) reduce tiempo de CPU en PHP puro. El uso de funciones específicas (por ejemplo, json_validate() si está disponible) evita sobrecarga por manejo de excepciones.

    4) Evitar sleep() en workflows asíncronos. Reemplaza bucles con sleep por event loops (ReactPHP, Swoole) o por colas con workers que esperan con mecanismos eficientes.

    5) OPcache y preload: habilita OPcache y pre-carga clases/archivos críticos para reducir interpretación por petición y el coste del autoloader en caliente.

    <?php
    require_once __DIR__ . '/vendor/autoload.php';
    require_once __DIR__ . '/src/BigFatClass.php';
    Lenguaje del código: PHP (php)

    6) Detectar fugas ocultas de CPU: logging síncrono excesivo, regex con backtracking y operaciones I/O sin batching pueden generar picos. Prueba patrones costosos y usa loggers asíncronos o batch writers.

    Ejemplos

    Aquí hay ejemplos listos para adaptar a tus jobs o endpoints problemáticos.

    <?php
    // Antes: procesar todo en memoria (riesgo de pico)
    foreach ($users as $user) {
        process($user);
    }
    Lenguaje del código: PHP (php)
    <?php
    // Después: procesar por lotes para limitar CPU por iteración
    foreach (array_chunk($users, 500) as $batch) {
        foreach ($batch as $user) {
            process($user);
        }
    }
    Lenguaje del código: PHP (php)
    <?php
    // Coalescencia de caché en Redis
    if ($lock = $redis->setnx("lock:feed", 1)) {
        $redis->expire("lock:feed", 30);
        $data = buildFeed();
        $redis->set("cache:feed", $data, 300);
    } else {
        $data = $redis->get("cache:feed");
    }
    Lenguaje del código: PHP (php)

    Checklist

    1. Identificar endpoints/jobs con mayor CPU y trazar hot paths.
    2. Reescribir bucles para procesamiento por lotes o paginación.
    3. Implementar coalescencia de caché (lock + fallback) para evitar stampedes.
    4. Delegar cómputo pesado a extensiones nativas cuando sea posible.
    5. Habilitar OPcache y preload para reducir interpretación y autoload en cada petición.
    6. Sustituir sleep() por event loops o colas con espera eficiente.

    Conclusión

    Los picos de CPU se solucionan con disciplina operativa y correcciones puntuales: controlar bucles, proteger la caché, delegar trabajo pesado y optimizar la carga de código. Implanta las medidas gradualmente y valida con métricas.

    Pequeñas correcciones en el código y la configuración suelen eliminar los picos de CPU más rápido que reescribir todo el sistema.

  • Seguridad de sesiones y cookies en PHP: guía práctica

    Seguridad de sesiones y cookies en PHP: guía práctica

    Resumen: Esta guía práctica resume las medidas esenciales para gestionar sesiones y cookies en PHP de forma segura: inicio de sesión, regeneración de ID, cookies seguras, timeouts, almacenamiento eficiente y limpieza al cerrar sesión.

    Introducción

    Sesiones y cookies permiten mantener estado en aplicaciones web PHP: autenticación, preferencias y datos temporales. Usadas correctamente mejoran la experiencia; mal configuradas, introducen riesgos de seguridad.

    Prerrequisitos

    Antes de aplicar las prácticas descritas asegúrate de servir las páginas sensibles por HTTPS y de tener control sobre el código que inicia y destruye sesiones. No afirmaré compatibilidades concretas; valida esto según tu entorno.

    Conocimiento mínimo necesario: saber dónde llamar a session_start() y cómo enviar cookies desde PHP. Evita exponer identificadores de sesión en URLs o registros.

    Desarrollo

    Procedimiento

    Pasos prácticos y concisos para asegurar sesiones y cookies en PHP. Aplique cada paso según el riesgo y la arquitectura de su aplicación.

    1. Iniciar sesiones de forma controlada y consistente.
    2. Regenerar el identificador de sesión tras la autenticación.
    3. Enviar cookies con flags Secure, HttpOnly y SameSite cuando corresponda.
    4. Implementar timeouts por inactividad y limpieza al logout.
    5. Almacenar solo identificadores en la sesión; datos pesados en base de datos o cache.
    6. Opcional: usar un session handler personalizado para almacenamiento centralizado.

    Detalles clave: regenerar el ID reduce el riesgo de session fixation; Secure y HttpOnly protegen el cookie en tránsito y frente a JavaScript; SameSite mitiga ciertas CSRF.

    Pro tip: Usa sesiones para identificar entidades (IDs) y no para persistir grandes objetos o datos sensibles sin cifrado.

    Ejemplos

    A continuación se incluyen ejemplos prácticos adaptados desde patrones comunes. Escapa los marcadores de apertura PHP en tu código según la plantilla del bloque.

    <?php
    // Start the session
    session_start();
    
    // Store some data in the session
    $_SESSION['username'] = 'JohnDoe';
    
    // Retrieve data from the session
    echo 'Hello, ' . $_SESSION['username']; // Outputs: Hello, JohnDoe
    Lenguaje del código: PHP (php)

    Regenerar el ID de sesión inmediatamente después de un login exitoso:

    <?php
    session_start();
    
    // After a successful login
    if ($_POST['username'] == 'user' && $_POST['password'] == 'password') {
        session_regenerate_id(true);  // Regenerate the session ID for added security
        
        $_SESSION['username'] = 'user';
        echo "Login successful, and your session ID has been regenerated!";
    }
    Lenguaje del código: PHP (php)

    Ejemplo de cookie segura con atributos recomendados (transmisión segura y protección contra acceso por JS):

    <?php
    // Set a cookie that only works on secure connections (HTTPS)
    setcookie('user_preference', 'dark_mode', [
        'expires' => time() + 3600, // 1 hour expiration
        'path' => '/',
        'domain' => 'yourdomain.com',
        'secure' => true,  // Send cookie only over HTTPS
        'httponly' => true,  // Prevent JavaScript access to cookie
        'samesite' => 'Strict' // Helps prevent CSRF attacks
    ]);
    
    echo 'Your preference cookie is set securely.';
    Lenguaje del código: PHP (php)

    Timeout de sesión por inactividad (mecanismo simple basado en marcas temporales):

    <?php
    session_start();
    
    // Set timeout period (10 minutes)
    $timeout = 600;
    
    if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $timeout)) {
        // If the session has expired, destroy it
        session_unset();
        session_destroy();
        echo 'Session expired. Please log in again.';
    } else {
        $_SESSION['LAST_ACTIVITY'] = time(); // Update last activity time
        echo 'Your session is still active.';
    }
    Lenguaje del código: PHP (php)

    Esqueleto de un session handler personalizado para almacenar sesiones en base de datos o cache centralizada:

    <?php
    class CustomSessionHandler extends SessionHandler {
        public function read($session_id) {
            // Read session data from the database
        }
    
        public function write($session_id, $data) {
            // Write session data to the database
        }
    }
    
    // Register custom session handler
    $handler = new CustomSessionHandler();
    session_set_save_handler($handler, true);
    session_start();
    Lenguaje del código: PHP (php)

    Al cerrar sesión, destruye la sesión y expira las cookies relevantes para evitar reutilización:

    <?php
    session_start();
    
    // Destroy the session and clear all session data
    session_unset();
    session_destroy();
    
    // Expire session and preference cookies
    setcookie(session_name(), '', time() - 3600, '/');
    setcookie('user_preference', '', time() - 3600, '/');
    
    echo 'Session and cookies have been cleared.';
    Lenguaje del código: PHP (php)

    Checklist

    1. Servir páginas sensibles por HTTPS.
    2. Iniciar sesión con session_start() solo donde sea necesario.
    3. Regenerar ID de sesión tras autenticación (session_regenerate_id).
    4. Enviar cookies con Secure, HttpOnly y SameSite adecuados.
    5. Implementar timeout por inactividad y actualizar LAST_ACTIVITY.
    6. Almacenar solo identificadores en la sesión; consultar DB para datos grandes.
    7. Destruir sesión y expirar cookies en logout.

    Conclusión

    La seguridad de sesiones y cookies es una combinación de buenas prácticas: transporte seguro (HTTPS), configuración correcta de cookies, rotación de identificadores, timeouts y limpieza al logout. Estas medidas reducen riesgos comunes como hijacking y XSS.

    Empieza aplicando los pasos del checklist y adapta las implementaciones (por ejemplo, handlers personalizados) según la escala y arquitectura de tu aplicación.